<?php
/**
 * this is where the core file sits
 * plus the only constant: the library path
 * which is the current directory
 */
// good habit: always use / at the end of the dir
define('_BLIBPATH', dirname(__file__) . '/');
define('BDS', '\\');
// require the basic elements without having to autoload
require_once _BLIBPATH . 'exception/base.php';

// register autoload here...
function __autoload($class)
{
    return Blib::autoLoadClass($class);

}// end __autoload

// for any uncaught exception, use dump
set_exception_handler('Blib::dump');

/**
 * simple core class: Blib
 * Blib
 * provide auto loading
 * nice exception dumping
 * and others...
 * @static no instance is required for this lib
 * @author bbqq <toughgamer@gmail.com>
 * @package com.blib.core
 * @uses PHP version 5.1+
 * @note Doesn't support PHP5.3 real namespace, but will still work
 * @copyright bbqq qstufie.com
 *
 * able to register self auto load path as well
 */
class Blib
{
    /**
     * auto load path
     * @var array
     */
    static protected $_loadPath = array(
        'blib_' => _BLIBPATH,
    );


    /**
     * the base url
     */
    static protected $_baseUrl = null;


    /**
     * @static
     * @var array of objects of singletones
     */
    static protected $_singletones = array();


    /**
     * stuff for benchmarking
     * @var array
     */
    static protected $_bvar = array();


    /**
     * current environment
     * @var str
     */
    static protected $_env = null;


    /**
     * configuration tools
     * @var array[env => conf]
     */
    static protected $_conf = array();


    /**
     * the registry
     * @var array
     */
    static protected $_reg = array();


    /**
     * allow debugging?
     * @var bool
     */
    static protected $_allowDebug = false;

/*======================================[auto load]======================================*/

    /**
     * register the auto load namespaces
     * @param str $ns the namespace
     * @param str $path the path
     * @return void
     */
    public static function registerLoadPath($ns, $path)
    {
        $ns = strtolower($ns);
        if ($ns == 'blib') {
            throw new Blib_Exception('Auto Load Setting Invalid: [blib] is reserved for internal use only');
        }

        // otherwise, add to memory and wait for load...
        self::$_loadPath[$ns . '_'] = $path;

    }// end registerLoadPath


    /**
     * get load path
     * @param str $ns the namespace
     * @return str
     */
    public static function getLoadPath($ns)
    {
        return !empty(self::$_loadPath[$ns . '_']) ? self::$_loadPath[$ns . '_'] : null;

    }// end getLoadPath


    /**
     * autoload a class
     * @param str $class the class name
     * @return void
     */
    public static function autoLoadClass($class)
    {
        if (class_exists($class)) {
            return true;
        }

        // validate the actual class name
        $path = strtolower($class);
        foreach (self::$_loadPath as $ns => $basePath) {
            // verify and load
            if (strpos($path, $ns) === 0) {
                // good... break it here...
                $path = str_replace($ns, '', $path);
                $path = $basePath . str_replace('_', '/', $path) . '.php';

                // try to include it...
                if (file_exists($path)) {
                    require_once $path;
                    return true;
                }
            }
        }

    }// end autoLoadClasss

/*======================================[runtime conf]======================================*/

    /**
     * set environment
     * @param str $env will be forced to lowercase if not, if null the current env will return
     * @return void
     */
    public static function env($env = null)
    {
        if (empty($env)) {
            return self::$_env;
        }
        // otherwise, set it to current...
        self::$_env = strtolower($env);

    }// end env


    /**
     * extend the configuration from a set env
     * NOTE: must set the current env then extend
     * @param str $env the existing env
     * @return void
     */
    public static function extendConf($env)
    {
        // check if env exists...
        if (!empty(self::$_conf[self::$_env])) {
            // no good, it already exists!
            throw new Blib_Exception('New Environment: ' . self::$_env . ' already exists in current environment.');
        }
        if (empty(self::$_conf[$env])) {
            // no good, it already exists!
            throw new Blib_Exception('Old Environment: ' . $env . ' doesn\'t exist');
        }
        // then it's time to do it right
        self::$_conf[self::$_env] = self::$_conf[$env];

    }// end extendConf


    /**
     * set conf
     * this goes at the run time level
     * at the current conf env.
     * @param str $key the key
     * @param mixed $val the value
     * @return void
     */
    public static function setConf($key, $val)
    {
        if (empty(self::$_conf[self::$_env][$key]) || !is_array(self::$_conf[self::$_env][$key])) {
            self::$_conf[self::$_env][$key] = $val;
        } else {
            self::$_conf[self::$_env][$key] = array_merge(self::$_conf[self::$_env][$key], $val);
        }

    }// end setConf


    /**
     * get configuration
     * @param str $key the key
     * @param mixed $default the default if not found.
     */
    public static function getConf($key, $default = null)
    {
        // 1st of all, check if exists
        if (empty(self::$_conf[self::$_env][$key])) {
            return $default;
        }

        // otherwise, send out the key...
        return self::$_conf[self::$_env][$key];

    }// end getConf

/*======================================[utilities]======================================*/

    /**
     * am I running under cli mode?
     * @return bool
     */
    public static function isCli()
    {
        return (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']));

    }// end isCli

    /**
     * dump the var
     * @return mixed
     */
    public static function dump()
    {
        // this takes infinite number of arguments
        $args = func_get_args();

        // cli? just simply dump the stuff out
        if (self::isCli()) {
            return var_dump($args);
        }

        // otherwise, a proper dump!
        foreach ($args as $arg) {
            if ($arg instanceof exception) {
                include _BLIBPATH . '_templates/exception.php';
            } else {
                echo '<pre>';
                self::_dump($arg);
                echo '</pre>';
            }
        }

    }// end dump


    /**
     * dump a single var...
     */
    static protected function _dump($var)
    {
        $var = var_export($var, 1);
        if (strpos($var, '<') !== false && strpos($var, '>') !== false) {
            // this is html code!
            $var = htmlentities($var);
        }
        echo stripslashes($var) . "\n";

    }// end _dump

/*======================================[bench marker]======================================*/

    /**
     * start bench marking
     * @param str $msg the message to add to it
     * @return void
     */
    static public function _bstart($msg = 'start')
    {
        if (!self::isDebugAllowed()) {
            return false;
        }
        // memory of this time:
        self::_bset($msg, 0);

    }// end bstart


    /**
     * set the current status
     * @protected
     * @param str $msg the message to add to it
     * @param double $intval the duration
     * @return void
     */
    static public function _bset($msg, $intval)
    {
        if (!self::isDebugAllowed()) {
            return false;
        }
        $mtime = Blib_Utility_Date::getMicroTime('', true);
        self::$_bvar[md5($mtime . $msg . $intval)] = array(
            'time' => $mtime,
            'duration' => $intval,
            'message' => $msg,
            'memory' => memory_get_usage()
        );
        // the basic time is set too...
        self::$_bvar['mtime'] = $mtime;

    }// end _bset


    /**
     * break points
     * @param str $msg the message to add to it
     * @return void
     */
    static public function _bp($msg =  null)
    {
        if (!self::isDebugAllowed()) {
            return false;
        }
        // break points...
        $mtime  = Blib_Utility_Date::getMicroTime('', true);
        $intval = number_format($mtime - self::$_bvar['mtime'], 4);
        // msg null? count it
        $msg = empty($msg) ? 'Break Point @' . $mtime : $msg;

        // message is not str?
        if (is_object($msg) || is_array($msg)) {
            $msg = '<pre>' . var_export($msg, 1) . '</pre>';
        }

        // memory of this time:
        self::_bset($msg, $intval);

    }// end _bp


    /**
     * report the status
     * @param bool $output if true, output as array
     * @return void
     */
    static public function _bstatus($output = false)
    {
        if (!self::isDebugAllowed()) {
            return false;
        }

        // if true: output
        if ($output) {
            return self::$_bvar;
        }

        // otherwise...
        $status = self::$_bvar;
        unset($status['mtime']);
        // dump it out.
        if (self::isCli()) {
            self::dump($status);
        } else {
            $status = self::$_bvar;
            unset($status['mtime']);
            include _BLIBPATH . '_templates/bench.php';
        }

    }// end _bstatus


    /**
     * function passthru bench marking
     * tool, uses a given function/class
     * to benchmark
     * @param str $function the function name
     * @param object $class must be either null or valid object
     * @return mixed - this one returns the original output, and the bench mark result
     * is saved to the usual bench mark variable which can be retrieved via
     * _bstatus
     */
    static public function _bench($fuction, $class = null)
    {
        // variables will follow the class...
        $params = func_get_args();
        if (count($params) <= 2) {
            $params = array();
        } else {
            unset($params[0], $params[1]);
        }

        if (!self::isDebugAllowed()) {
            // just passthru directly
            if (!empty($class)) {
                return call_user_func_array(array($class, $fuction), $params);
            }

            return call_user_func($fuction, $params);

        }// if no debug enabled...

        // start bench here...
        $mtime = Blib_Utility_Date::getMicroTime('', true);
        // do the job
        if (!empty($class)) {
            $result = call_user_func_array(array($class, $fuction), $params);
        } else {
            $result = call_user_func($fuction, $params);
        }
        $intval = number_format(Blib_Utility_Date::getMicroTime('', true) - $mtime, 4);
        self::_bset('Pass thru: ' . get_class($class) . '->' . $fuction, $intval);
        return $result;

    }// end _bench


    /**
     * is debugging allowed?
     */
    static public function allowDebug($allow)
    {
        self::$_allowDebug = (bool) $allow;
        // also start bench
        self::_bstart('start benchmarking');

    }// end allowDebug


    /**
     * is debugging allowed?
     * @return bool
     */
    static public function isDebugAllowed()
    {
        return self::$_allowDebug;

    }// end isDebugAllowed

/*======================================[singleton]======================================*/

    /**
     * force singletones out
     * @param str $class the class name
     * @param array $params optional, the param to pass to the class
     * @return $class the actual singleton instance of that class
     */
    public static function getSingleton($class, $params = array())
    {
        // first, check if it's registered already
        if (isset(self::$_singletones[$class]) && self::$_singletones[$class] instanceof $class) {
            return self::$_singletones[$class];
        }

        // otherwise, do it and put it out
        $instance = new $class($params);
        self::$_singletones[$class] = $instance;
        unset($instance);
        return self::$_singletones[$class];

    }// end getSingleton


    /**
     * clear the singleton instances
     * @param str $class the class name, if omitted, clear all
     * @return void
     */
    public static function clearSingleton($class = null)
    {
        if (empty($class)) {
            foreach (self::$_singletones as $key => $singleton) {
                unset(self::$_singletones[$key]);
            }
        }

        if (isset(self::$_singletones[$class]) && self::$_singletones[$class] instanceof $class) {
            unset(self::$_singletones[$class]);
        }

    }// end clearSingletone

/*======================================[registry]======================================*/

    /**
     * register the object
     * @param $key
     * @param $object
     * @param $overwrite default to true
     * @return void
     */
    static public function register($key, $object, $overwrite = true)
    {
        if (isset(self::$_reg[$key]) && !$overwrite) {
            throw new Exception('KEY: ' . $key . ' Already Exists');
        }

        self::$_reg[$key] = $object;

    }// end register


    /**
     * remove object by key
     * @param str $key
     * @return void
     */
    static public function remove($key)
    {
        if (isset(self::$_reg[$key])) {
            unset(self::$_reg[$key]);
        }

    }// end remove


    /**
     * remove by finding the right name...
     * @param str $find
     * @return unknown_type
     */
    static public function removeNs($find)
    {
        foreach (self::$_reg as $k => $v) {
            if (strpos($k, $find) === 0) {
                unset(self::$_reg[$k]);
            }
        }

    }// end removeNs


    /**
     * get object by key
     * @param str $key
     * @return void
     */
    static public function get($key)
    {
        if (isset(self::$_reg[$key])) {
            return self::$_reg[$key];
        }

        return false;

    }// end remove


    /**
     * clear all stored objects
     * @return void
     */
    static public function clearAll()
    {
        foreach (self::$_reg as $obj) {
            unset($obj);
        }

        self::$_reg = array();

    }// end clearAll


    /**
     * clears the pdo connection
     * by searching through the vals in it
     * and unset each.
     * @return array a log array of pdo connections cleared...
     */
    static public function clearPdo()
    {
        $logs = array();
        foreach (self::$_reg as $key => &$val) {
            if ($val instanceof Blib_Data_Dao_Wrapper_Pdo) {
                $val->purgeAll();
                $val = null;
                $logs[$key] = 'PDO: ' . $key . ' connection is closed.';
            }
        }

        return $logs;

    }// end clearPdo

/*======================================[file management]======================================*/

    /**
     * include external objects...
     * @param str $path path relative to /external/ directory
     * @return void
     */
    static public function import($path)
    {
        include_once _BLIBPATH . 'external/' . $path;

    }// end import

/*======================================[post/get]======================================*/

    /**
     * is it post?
     * @return void
     */
    static public function isPost()
    {
        return !empty($_POST);

    }// end isPost


    /**
     * is it post?
     * @return void
     */
    static public function isGet()
    {
        return empty($_POST) && !empty($_GET);

    }// end isPost


    /**
     * get var
     * @param str $key the key
     * @param str $default default value
     * @param str $type POST|GET default is POST
     * @return void
     */
    static public function getInput($key, $default = null, $type = 'POST')
    {
        if ($type != 'POST') {
            $arSrc = $_GET;
        } else {
            $arSrc = $_POST;
        }

        if (!empty($arSrc[$key])) {
            return $arSrc[$key];
        }

        return $default;

    }// end getInput


    /**
     * get the base url out
     * @return str
     */
    static public function baseUrl()
    {
        // if cli, return nothing
        if (self::isCli()) {
            return null;
        }

        if (empty(self::$_baseUrl)) {
            self::$_baseUrl = dirname($_SERVER['PHP_SELF']) . '/';
        }

        return self::$_baseUrl;

    }// end baseUrl

}// end Blib
?>